/**
 * Project: apollo-base-tools
 * 
 * File Created at 2017年8月7日
 * 
 * Copyright 2015-2016 dx.com Croporation Limited.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * DongXue software Company. ("Confidential Information").  You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with dx.com.
 */
package org.apollo.sqlite.utils;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Date;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apollo.sqlite.anno.Id;
import org.apollo.sqlite.anno.Skip;
import org.apollo.sqlite.anno.Table;
/** 
* @ClassName: MapperUtil 
* @Description: mapping工具：表名称、反射
* @author wuzhenfang(wzfbj2008@163.com)
* @date 2017年8月7日 上午12:26:28 
* @version V1.0 
*/
public class MapperUtil {

	private final static String ID = "pk_id";

	/****** 反射工具类，统一管理反射 *******/
	public static <T> T newInstance(Class<T> clazz) throws IllegalAccessException, InstantiationException {
		return clazz.newInstance();
	}

	public static Field[] getDeclaredFields(Class<?> clazz) {
		return clazz.getDeclaredFields();
	}

	public static Field getDeclaredField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
		return clazz.getDeclaredField(fieldName);
	}

	public static Object getFieldValue(Field field, Object object) throws IllegalAccessException {
		if (!field.isAccessible()) {
			field.setAccessible(true);
		}
		return field.get(object);
	}

	/**
	 *
	 * @param object 类实例
	 * @return 表名
	 */
	public static String getTableName(Object object) {
		return getTableName(object.getClass());
	}

	/**
	 * 获取表名,如果标识注解为table则取table的value否则直接去class的名
	 * @param clazz 类
	 * @return 表名
	 */
	public static String getTableName(Class<?> clazz) {
		Annotation[] annos = clazz.getDeclaredAnnotations();
		for (Annotation annotation : annos) {
			if (annotation instanceof Table) {
				String tableName = ((Table) annotation).value();
				if (StringUtils.isEmpty(tableName)) {
					break;
				}
				return tableName;
			}
		}
		return clazz.getName().replace(".", "_");
	}

	/**
	 * 判断是否为忽略字段，非数据库字段
	 * @param field
	 * @return
	 */
	public static boolean isSkipField(Field field) {
		return Modifier.isStatic(field.getModifiers()) // 移除是final和static的字段
				|| Modifier.isFinal(field.getModifiers()) || field.getAnnotation(Skip.class) != null;
	}

	/**
	 * 获取id 字段
	 * @param clazz class
	 * @return null is not found
	 */
	public static Field getIDField(Class<?> clazz) {
		return getIDField(getDeclaredFields(clazz));
	}

	/**
	 * 获取id 字段
	 * @param fields class.getDeclaredFields()
	 * @return null is not found
	 */
	public static Field getIDField(Field[] fields) {
		Field idField = null;
		if (fields != null && fields.length != 0) {
			// 1. 寻找id注解的field，并检查id注解是否只有一个
			for (Field field : fields) {
				if (isSkipField(field)) {
					continue;
				}
				Id id = field.getAnnotation(Id.class);
				if (id != null) {
					if (idField == null) {
						idField = field;
					} else {
						throw new IllegalStateException("a class must have only one ID annotation");
					}
				}
			}
			// 2. 没有id注解，只能用field名来查找
			if (idField == null) {
				for (Field field : fields) {
					if (isSkipField(field)) {
						continue;
					}
					String name = field.getName();
					if (ID.equals(name)) {
						return field;
					}
				}
			}
		}
		return idField;
	}

	/**
	 * 获取id String
	 *
	 * @param objectClass 类
	 * @return id String
	 * @throws IllegalAccessException IllegalAccessException
	 */
	public static String getIdName(Class objectClass) throws IllegalAccessException {
		String name = null;
		Field field = getIDField(objectClass);
		if (field != null) {
			name = field.getName();
			Id id = field.getAnnotation(Id.class);
			if (id != null && !"".equals(id.column())) {
				name = id.column();
			}
		}
		return name;
	}

	/**
	 * 获取id的属性名和属性值
	 * @param object 对象
	 * @return 属性（名字和值）
	 * @throws IllegalAccessException IllegalAccessException
	 */
	public static KeyValue getIDColumn(Object object) throws IllegalAccessException {
		Class objectClass = object.getClass();
		Field[] fields = getDeclaredFields(objectClass);
		Field field = getIDField(fields);
		// 1. 判断null
		if (field != null) {
			Id id = field.getAnnotation(Id.class);
			String name = field.getName();
			// 判断是否有ID Annotation
			if (id != null && !"".equals(id.column())) {
				name = id.column();
			}
			Object value = getFieldValue(field, object);
			KeyValue column = new KeyValue();
			column.setKey(name);
			column.setValue(value);
			return column;
		}
		return null;
	}

	/**
	 * map结果集转化成实例
	 * @param type
	 * @param map
	 * @return
	 */
	public <T> T resultToBean(Class<T> type, Map<String, Object> map) {
		try {
			BeanInfo beanInfo = Introspector.getBeanInfo(type); // 获取类属性
			Object object = type.newInstance(); // 创建 JavaBean 对象
			// 给 JavaBean 对象的属性赋值
			PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
			for (int i = 0; i < propertyDescriptors.length; i++) {
				PropertyDescriptor descriptor = propertyDescriptors[i];
				String propertyName = descriptor.getName();
				if (map.containsKey(propertyName)) {
					// 下面一句可以 try 起来，这样当一个属性赋值失败的时候就不会影响其他属性赋值。
					Object value = map.get(propertyName);
					Object[] args = new Object[1];
					args[0] = value;
					descriptor.getWriteMethod().invoke(object, args);
				}
			}
			return (T) object;
		} catch (Exception e) {
			// TODO: handle exception
		}
		return null;
	}
	
	/**
	 * 通过反射机制获取object中属性值，添加到ContentValues中
	 * @param object 对象
	 * @return ContentValues
	 * @throws IllegalAccessException
	 */
	public static ContentValues getContentValues(Object object) throws IllegalAccessException {
		ContentValues values = new ContentValues();
		Class<?> clazz = object.getClass();
		Field[] fields = getDeclaredFields(clazz);
		for (Field field : fields) {
			if (isSkipField(field)) {
				continue;
			}
			Object value = getFieldValue(field, object);
			if (value == null) {
				continue;
			}
			String name = field.getName();

			if (value instanceof String) {
				values.put(name, String.valueOf(value));
			} else if (value instanceof Double || value.getClass() == double.class) {
				values.put(name, Double.valueOf(String.valueOf(value)));
			} else if (value instanceof Float || value.getClass() == float.class) {
				values.put(name, Float.valueOf(String.valueOf(value)));
			} else if (value instanceof Long || value.getClass() == long.class) {
				values.put(name, Long.valueOf(String.valueOf(value)));
			} else if (value instanceof Integer || value.getClass() == int.class) {
				values.put(name, Integer.valueOf(String.valueOf(value)));
			} else if (value instanceof Short || value.getClass() == short.class) {
				values.put(name, Short.valueOf(String.valueOf(value)));
			} else if (value instanceof Byte || value.getClass() == byte.class) {
				values.put(name, Byte.valueOf(String.valueOf(value)));
			} else if (value instanceof Boolean || value.getClass() == boolean.class) {
				values.put(name, Boolean.valueOf(String.valueOf(value)));
			} else if (value instanceof byte[]) {
				values.put(name, String.valueOf(value).getBytes());
			} else if (value instanceof Date) {
				values.put(name, ((Date) value).getTime());
			} else {
				values.put(name, object2Byte(value));
			}
		}
		return values;
	}

	/**
	 * 通过反射机制获取object中属性值，添加到ContentValues中，除了ID
	 * @param object 对象
	 * @return ContentValues
	 * @throws IllegalAccessException
	 */
	public static ContentValues getContentValuesWithOutID(Object object) throws IllegalAccessException {
		ContentValues values = new ContentValues();
		Class<?> clazz = object.getClass();
		Field[] fields = getDeclaredFields(clazz);
		for (Field field : fields) {
			if (isSkipField(field)) {
				continue;
			}
			Id id = field.getAnnotation(Id.class);
			if (id != null) {
				continue;
			}

			Object value = getFieldValue(field, object);
			if (value == null) {
				continue;
			}

			String name = field.getName();
			if (value instanceof String) {
				values.put(name, String.valueOf(value));
			} else if (value instanceof Double || value.getClass() == double.class) {
				values.put(name, Double.valueOf(String.valueOf(value)));
			} else if (value instanceof Float || value.getClass() == float.class) {
				values.put(name, Float.valueOf(String.valueOf(value)));
			} else if (value instanceof Long || value.getClass() == long.class) {
				values.put(name, Long.valueOf(String.valueOf(value)));
			} else if (value instanceof Integer || value.getClass() == int.class) {
				values.put(name, Integer.valueOf(String.valueOf(value)));
			} else if (value instanceof Short || value.getClass() == short.class) {
				values.put(name, Short.valueOf(String.valueOf(value)));
			} else if (value instanceof Byte || value.getClass() == byte.class) {
				values.put(name, Byte.valueOf(String.valueOf(value)));
			} else if (value instanceof Boolean || value.getClass() == boolean.class) {
				values.put(name, Boolean.valueOf(String.valueOf(value)));
			} else if (value instanceof byte[]) {
				values.put(name, String.valueOf(value).getBytes());
			} else if (value instanceof Date) {
				values.put(name, ((Date) value).getTime());
			} else {
				values.put(name, object2Byte(value));
			}
		}
		return values;
	}

	/**
	 * 获取id的数据类型
	 * @param clazz 类
	 * @return 字段的数据类型
	 */
	public static SqliteDataType getDataType(Class<?> clazz) {
		if (clazz == int.class || clazz == Integer.class || clazz == byte.class || clazz == Byte.class || clazz == short.class || clazz == Short.class || clazz == long.class
				|| clazz == Long.class || clazz == Boolean.class || clazz == boolean.class || clazz == Date.class) {
			return SqliteDataType.INTEGER;
		} else if (clazz == float.class || clazz == Float.class || clazz == double.class || clazz == Double.class) {
			return SqliteDataType.REAL;
		} else if (clazz == String.class) {
			return SqliteDataType.TEXT;
		} else {
			return SqliteDataType.BLOB;
		}
	}

	/**
	 * 构建 对象，class实例化对象，游标获取数据填充对象
	 * @param objectClass 对象类
	 * @param cursor 游标
	 * @return 填充数据后的对象
	 * @throws InstantiationException InstantiationException
	 * @throws IllegalAccessException IllegalAccessException
	 * @throws NoSuchFieldException NoSuchFieldException
	 * @throws ParseException NoSuchFieldException
	 */
	public static <T> T buildObject(Class<T> objectClass, Cursor cursor) throws InstantiationException, IllegalAccessException, NoSuchFieldException {
		T object = newInstance(objectClass);
		int columnCount = cursor.getColumnCount();
		for (int i = 0; i < columnCount; i++) {
			if (cursor.isNull(i)) {
				continue;
			}
			String columnName = cursor.getColumnName(i);
			Field field = getDeclaredField(objectClass, columnName);
			if (field == null) {
				continue;
			}
			Class fieldClass = field.getType();
			if (!field.isAccessible()) {
				field.setAccessible(true);
			}
			if (fieldClass == String.class) {
				field.set(object, cursor.getString(i));
			} else if (fieldClass == double.class || fieldClass == Double.class) {
				field.set(object, cursor.getDouble(i));
			} else if (fieldClass == float.class || fieldClass == Float.class) {
				field.set(object, cursor.getFloat(i));
			} else if (fieldClass == Long.class || fieldClass == long.class) {
				field.set(object, cursor.getLong(i));
			} else if (fieldClass == Integer.class || fieldClass == int.class) {
				field.set(object, cursor.getInt(i));
			} else if (fieldClass == byte.class || fieldClass == Byte.class) {
				String value = cursor.getString(i);
				try {
					field.set(object, Byte.parseByte(value));
				} catch (NumberFormatException e) {
					e.printStackTrace();
				}
			} else if (fieldClass == Short.class || fieldClass == short.class) {
				short value = cursor.getShort(i);
				field.set(object, value);
			} else if (fieldClass == byte[].class) {
				byte[] value = cursor.getBlob(i);
				field.set(object, value);
			} else if (fieldClass == Boolean.class || fieldClass == boolean.class) {
				int value = cursor.getInt(i);
				boolean bValue = value != 0;
				field.set(object, bValue);
			} else if (fieldClass == Date.class) {
				long value = cursor.getLong(i);
				field.set(object, new Date(value));
			} else {
				byte[] value = cursor.getBlob(i);
				field.set(object, byte2Object(value));
			}
		}
		return object;
	}

	public static byte[] object2Byte(Object object) {
		ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
		ObjectOutputStream objectOutputStream = null;
		try {
			objectOutputStream = new ObjectOutputStream(arrayOutputStream);
			objectOutputStream.writeObject(object);
			objectOutputStream.flush();
			return arrayOutputStream.toByteArray();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (objectOutputStream != null) {
					objectOutputStream.close();
				}
				arrayOutputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}

	public static Object byte2Object(byte[] data) {
		ByteArrayInputStream arrayInputStream = new ByteArrayInputStream(data);
		ObjectInputStream inputStream = null;
		try {
			inputStream = new ObjectInputStream(arrayInputStream);
			return inputStream.readObject();

		} catch (IOException | ClassNotFoundException e) {
			e.printStackTrace();
		} finally {
			try {
				if (inputStream != null) {
					inputStream.close();
				}
				arrayInputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}
}
